#!/usr/bin/python -S
"""execute.py

Interpreter for the pattern.
"""

__author__ = 'Andy Chu'

ERR_WINDOW = 15  # How much context to show in errors

import parse  # Just for filter types


def log(s, *args):
  print '***', s % args


def _Collect(results):
  if results is None:
    return None

  collected = {}
  for result in results:
    if isinstance(result, dict):
      collected.update(result)
    else:
      # A single item
      return result

  return collected


def _MakeValue(value, block, workspace):

  # Apply the filter to the value
  for filter_type, func in block.filters:
    if filter_type == parse.ACTION:
      value = func(value, workspace)
      print 'stack:', workspace
    elif filter_type == parse.PATTERN:
      # TODO: Implement
      value = func(value)
    else:
      value = func(value)

  # Return the value named in a dictionary, unless it's anonynmous
  name = block.name 
  if name and name != '@':  # TODO: May not need @ anymore
    value = {name: value}

  return value
    

def _MatchLiteral(arg, result, s, pos):
  """Match a literal string."""

  cur = s[pos:]
  if cur.startswith(arg):
    newpos = pos + len(arg)
    result.log("Matched literal %r against %r (%s)", arg,
        s[pos : pos+ERR_WINDOW], newpos) 
    return {}, newpos
  else:
    result.log("Can't match literal %r against %r (%s)", arg,
        s[pos : pos+ERR_WINDOW], pos) 
    return None, pos


def _MatchRegex(arg, result, s, pos):
  """Match an unnamed regex."""

  regex = arg
  match = regex.match(s, pos)
  result.match_count += 1
  if match:
    # Advance the position, no capture
    return {}, match.end()

  else:  # No match
    result.log(
        "Can't match regex %r against %r (%s)", regex.pattern,
        s[pos : pos+ERR_WINDOW], pos) 
    return None, pos


def _MatchAndCaptureGroup(arg, result, s, pos):
  """Match an unnamed regex."""

  block = arg
  fragments = block.Fragments()

  assert len(fragments) == 1  # Should be caught at compile time
  # HACK:  IGNORE _MatchRegex here.  We need to capture, but _MatchRegex should
  # return an (empty) dictionary.
  _, regex = fragments[0]  

  match = regex.match(s, pos)
  result.match_count += 1

  if match:
    # TODO: Could have a full Python slice syntax for selecting groups.  Keep it
    # simple for now though.
    if block.index:
      value = match.group(block.index)
    else:
      value = list(match.groups())  # None means all groups
    value = _MakeValue(value, block, result.workspace)
    return value, match.end()

  else:  # No match
    result.log(
        "Can't match regex %r against %r (%s)", regex.pattern,
        s[pos : pos+ERR_WINDOW], pos) 
    return None, pos


def _MatchValue(arg, result, s, pos):
  """Value {/value name} {field1 \s+} {field2 \w+} {/end}
  """
  block = arg
  results, newpos = _MatchFragments(block.Fragments(), result, s, pos)

  if results is None:
    return None, newpos

  if block.scalar:
    value = s[pos:newpos]  # All text between delimiters
  else:
    value = _Collect(results)  # All captured values between delimiters

  return _MakeValue(value, block, result.workspace), newpos


def _MatchOptional(arg, result, s, pos):
  """Optional value {optional name} {field1 \s+} {field2 \w+} {end}

  This is similar to _MatchValue, except it doesn't return None to abort the
  whole match.  It just returns {} or ''.
  """
  block = arg
  results, p = _MatchFragments(block.Fragments(), result, s, pos)

  if results is None:
    newpos = pos  # Didn't go anywhere
    if block.scalar:
      value = ''
    else:
      value = {}
  else:
    newpos = p
    if block.scalar:
      value = s[pos:newpos]  # All text between delimiters
    else:
      value = _Collect(results)  # All captured values between delimiters

  return _MakeValue(value, block, result.workspace), newpos


def _MatchRepeated(arg, result, s, pos):
  block = arg
  captured = []
  i = 0

  p = pos  # position advanced on each iteration
  newpos = pos  # return value

  while True:
    i += 1  
    results, p = _MatchFragments(block.Fragments(), result, s, p)
    if results is None:  # No match.
      result.log('Stopping iteration at iteration %s' % i)
      break
    else:
      captured.append(_Collect(results))
      # It's captured; move return value forward
      newpos = p

  if block.scalar:
    value = s[pos:newpos]
  else:
    value = captured

  value = _MakeValue(value, block, result.workspace)
  return value, newpos


def _MatchChoices(arg, result, s, pos):
  block = arg
  # The structure of a choice block is a list of blocks
  for fragments in block.Fragments():
    results, p = _MatchFragments(fragments, result, s, pos)
    if results is not None:
      newpos = p
      if block.scalar:
        value = s[pos:newpos]
      else:
        value = _Collect(results)
      return _MakeValue(value, block, result.workspace), newpos

  # No fragment matched
  return None, pos


def _MatchSubpattern(arg, result, s, pos):
  """For {:int hour}"""

  block = arg
  pattern = block.pattern

  subresult = pattern.match(s, pos)
  if subresult.data is None:
    result.log(
        "Can't match subpattern %s against %r (%s)", pattern,
        s[pos : pos+ERR_WINDOW], pos)
    result.log(subresult.log_entries)
    return None, pos
  else:
    return _MakeValue(subresult.data, block, result.workspace), subresult.pos


def _MatchFragments(fragments, result, s, pos):
  """Match a string and return a dictionary.

  Args:
    fragments: A list of fragments to match
    result:

  This is called in a mutually recursive fashion.
  """
  captured = []

  for i, fragment in enumerate(fragments):
    func, arg = fragment
    #log('Calling %s with %s', func, arg)

    data, pos = func(arg, result, s, pos)
    if data is None:  # propagate non-matches
      return None, pos
    else:
      captured.append(data)

  return captured, pos
